package away3d.animators;


import flash.geom.Vector3D ;
import away3d.animators.skeleton.SkeletonAdditiveNode ;
import away3d.animators.skeleton.SkeletonClipNodeBase ;
import away3d.animators.skeleton.SkeletonNaryLERPNode ;
import away3d.animators.skeleton.SkeletonTimelineClipNode ;
import away3d.animators.skeleton.SkeletonTreeNode ;
import away3d.animators.data.SkeletonAnimationSequence ;
import away3d.animators.data.SkeletonAnimationState ;
import away3d.animators.data.AnimationSequenceBase ;
import away3d.animators.data.AnimationStateBase ;



/**
 * AnimationSequenceController provides a controller for single clip-based animation sequences (fe: md2, md5anim).
 */
class BlendingSkeletonAnimator extends SkeletonAnimatorBase
{
    
    
    private var _clips          : Array<SkeletonClipNodeBase> ;// TODO: check the type of this array just a quick guess at mo
    private var _lerpNode       : SkeletonNaryLERPNode ;
    private var _mainNode       : SkeletonTreeNode ;
    private var _additiveNodes  : Array<SkeletonClipNodeBase> ;// TODO: check the type of this array just a quick guess at mo
    private var _activeAbsClips : Vector<Int> ;
    private var _numActiveClips : Int;//uint ?
    private var _blendWeights   : Vector<Float> ;
    
    
    
    /** 
     * Creates a new AnimationSequenceController object.
     */
    public function new( target : SkeletonAnimationState)
    {
        
        super( target );
        
        _clips              = [];
        _activeAbsClips     = new Vector();
        _blendWeights       = new Vector();
        _additiveNodes      = [];
        
    }
    
    
    private override function createBlendTree() : SkeletonTreeNode
    {
        
        _lerpNode = new SkeletonNaryLERPNode();
        
        // mainNode also necessary since might be different from lerpNode when using additive blending
        _mainNode = _lerpNode;
        
        return _lerpNode;
        
    }
    
    
    public function setWeight( clipName : String, weight : Float )
    {
        
        if( weight > 1 )
        {
            
            weight = 1 ;
            
        }
        else if( weight < 0 ) 
        {
            
            weight = 0 ;
            
        }
        
        var clip /*: SkeletonClipNodeBase*/ = _clips[ clipName ] ;
        
        if( !clip ) return ;
        
        if( clip.clip._additive )
        {
            
            SkeletonAdditiveNode( _additiveNodes[ clipName ] ).blendWeight = weight ;
        } 
        else 
        {
        
            setLERPWeightFor(clip, weight) ;
            
        }
        
    }
    
    
    private function setLERPWeightFor( clip : SkeletonClipNodeBase, weight : Float )
    {
        
        var inputIndex                  = _lerpNode.getInputIndex( clip );
        var total                       = 0;
        var i           : Int ;
        var lerpIndex   : Int ;
        var realWeights                 = _lerpNode.blendWeights ;
        lerpIndex                       = _activeAbsClips.indexOf( inputIndex ) ;
        _blendWeights[ inputIndex ]     = weight ;
        
        if( weight == 0 )
        {
            
            // remove from active clips if weight = 0;
            realWeights[ lerpIndex ] = 0 ;
            if( lerpIndex >= 0 )
            {
                // remove item (faster than splice)
                _activeAbsClips[ lerpIndex ] = _activeAbsClips[ --_numActiveClips ] ;
                _activeAbsClips.pop() ;
                
            }
            
        }
        else 
        {
            
            if( lerpIndex < 0 ) _activeAbsClips[ _numActiveClips++ ] = inputIndex ;
            
        }
        
        // recalculate weights, so all add to 1
        for( i in 0..._numActiveClips )
        {
            
            lerpIndex                   = _activeAbsClips[ i ] ;
            total                       += _blendWeights[ lserpIndex ] ;
            
        }
        
        for( i in 0..._numActiveClips )
        {
            
            lerpIndex                   = _activeAbsClips[ i ] ;
            realWeights[ lerpIndex ]    = _blendWeights[ lerpIndex ] / total ;
            
        }
        
        _lerpNode.updateWeights(realWeights);
        
    }
    
    
    /**
     * Adds a sequence to the controller.
     * Differentiate between timeline based and phase? Or should be property of sequence?
     */
     public function addSequence( sequence : SkeletonAnimationSequence )
    {
        
        if( sequence._additive )
        {
            
            addAdditiveSequence( sequence ) ;
            
        }
        else
        {
            
            addAbsoluteSequence( sequence ) ;
            
        }
        
    }
    
    
    private function addAdditiveSequence( sequence: SkeletonAnimationSequence )
    {
        
        var additive                    = new SkeletonAdditiveNode();
        var node                        = new SkeletonTimelineClipNode();
        node.clip                       = sequence ;
        _clips[ sequence.name ]         = node;
        _additiveNodes[ sequence.name ] = additive;
        additive.differenceInput        = node;
        additive.baseInput              = _mainNode;
        _mainNode                       = additive;
        _target.blendTree               = _mainNode;
        
    }
    
    
    private function addAbsoluteSequence( sequence: SkeletonAnimationSequence )
    {
        
        _blendWeights[_blendWeights.length] = 0 ;
        var node                            = new SkeletonTimelineClipNode() ;
        _clips[ sequence.name ]             = node ;
        node.clip                           = sequence ;
        _lerpNode.addInput( node ) ;
        
    }
    
    
    /**
     * @inheritDoc  
     * @private
     */
    private override function updateAnimation( realDT: Float, scaledDT: Float )
    {
        
        if( _numActiveClips > 0 )
        {
            
            _mainNode.time += scaledDT / _mainNode.duration ;
            super.updateAnimation( realDT, scaledDT ) ;
        
        }
        
    }
    
    /**
     * Retrieves a sequence with a given name.
     * @private
     */
    public function getSequence( sequenceName: String ) : AnimationSequenceBase
    {
         
        return _clips[ sequenceName ];
        
    }
    
    
    public function play()
    {
        
        start() ;
        
    }
    
    
}